package main

import (
	"fmt"
	"github.com/afex/hystrix-go/hystrix"
	"math/rand"
	"time"
)

type Product struct {
	ID    int
	Title string
	Price int
}

// 远端商品，模拟超时
func getProduct() (Product, error) {
	r := rand.Intn(10) // 0 <= r < 10
	if r < 6 {
		fmt.Println("卡顿3秒")
		time.Sleep(time.Second * 3)
	}
	return Product{
		ID:    101,
		Title: "Golang 从入门到精通",
		Price: 2,
	}, nil
}

// RecProduct 调用远端出错，使用本地商品，实现服务降级
func RecProduct() (Product, error) {
	return Product{
		ID:    999,
		Title: "推荐商品",
		Price: 22,
	}, nil
}

func main() {
	rand.Seed(time.Now().Unix()) // 设置随机因子

	// 配置时间，当调用的接口或服务在配置的时间内没有返回时，就执行服务降级
	hystrix.ConfigureCommand("getProduct", hystrix.CommandConfig{
		Timeout:               2000, // 表示超过2秒时间没有响应，调用服务降级方法
		MaxConcurrentRequests: 3,    // 最大3个并发

		// 当请求数符合配置的熔断器参数，熔断器会自动打开。执行降级，表现在服务降级后返回的参数特别的快
		RequestVolumeThreshold: 3,  // 熔断器请求阀值。意思是有3个请求才才进行错误白反比计算,默认20
		ErrorPercentThreshold:  20, // 错误百反比。默认 50（50%）

		// 熔断器3种状态
		SleepWindow: int(time.Second * 100), // 熔断器打开后，多少毫秒尝试恢复，建议5到10秒
	})

	// 获取熔断器状态
	circuit, _, _ := hystrix.GetCircuit("getProduct")

	// 模拟超时请求，调用其他接口或服务超时没返回时，使用 hystrix 做服务容错
	for {
		resultChan := make(chan Product, 1)
		// 异步 hystrix.Go 执行
		errs := hystrix.Go(
			"getProduct",
			func() error { // 正常业务逻辑
				product, err1 := getProduct()
				if err1 != nil {
					return err1 // 会被参数三函数入参得到
				}
				resultChan <- product
				return nil
			},
			func(err error) error { // 服务降级业务逻辑,使用本地服务
				fmt.Println(err) // hystrix: max concurrency 超过并发数
				product, err2 := RecProduct()
				if err2 != nil {
					return err2 // 会被errs捕获
				}
				resultChan <- product
				return nil
			},
		)
		// 打印最终的结果
		select {
		case product := <-resultChan:
			fmt.Println(product.ID, product.Title, product.Price)
		case err := <-errs:
			fmt.Println(err)
		}

		// 熔断器状态
		if circuit.IsOpen() {
			fmt.Println("熔断器打开")
		} else {
			fmt.Println("熔断器关闭")
		}

		time.Sleep(time.Second * 1)
	}
}
